
#include <avr/io.h>
#include "Arduino.h"

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "time.h"
#include "debug.h"

#include "cmd.h"
#include "lcd.h"

//
// ADC voltages for the 5 buttons on analog input pin A0:
//  
//    RIGHT:  0.00V :   0 @ 8bit ;   0 @ 10 bit
//    UP:     0.71V :  36 @ 8bit ; 145 @ 10 bit
//    DOWN:   1.61V :  82 @ 8bit ; 329 @ 10 bit
//    LEFT:   2.47V : 126 @ 8bit ; 505 @ 10 bit
//    SELECT: 3.62V : 185 @ 8bit ; 741 @ 10 bit
//

//
// Pins in use
//
#define BUTTON_ADC_PIN           A0  // A0 is the button ADC input

//#define LCD_BACKLIGHT_PIN         3  // D3 controls LCD backlight
// ADC readings expected for the 5 buttons on the ADC input
#define RIGHT_10BIT_ADC           0  // right
#define UP_10BIT_ADC            145  // up
#define DOWN_10BIT_ADC          329  // down
#define LEFT_10BIT_ADC          505  // left
#define SELECT_10BIT_ADC        741  // right
#define BUTTONHYSTERESIS         10  // hysteresis for valid button sensing window

//some example macros with friendly labels for LCD backlight/pin control, tested and can be swapped into the example code as you like
//#define LCD_BACKLIGHT_OFF()     digitalWrite( LCD_BACKLIGHT_PIN, LOW )
//#define LCD_BACKLIGHT_ON()      digitalWrite( LCD_BACKLIGHT_PIN, HIGH )
//#define LCD_BACKLIGHT(state)    { if( state ){digitalWrite( LCD_BACKLIGHT_PIN, HIGH );}else{digitalWrite( LCD_BACKLIGHT_PIN, LOW );} }

//#define DEBOUNCE_TICKS   1000   // in ticks
#define DEBOUNCE_TICKS     getTicksFromMillis(100)

extern QueueHandle_t CmdQueue;

static int8_t lastButtonState; // the previous reading from the button pin
static uint32_t lastDebounceTime;

#define CTR_STATE_UNKNOWN   0
#define CTR_STATE_ROOT      1
#define CTR_STATE_MENU      2

static uint8_t state;

#if 0
uint8_t CmdTable[] PROGMEM =
{
  "Unknown",
  "Display"
};

// Retrieving: byte = pgm_read_byte(&(CmdTable[i]));
#endif // 0

// ===================================================================
//   CMD_UpdateDisplay
// ===================================================================
void CMD_UpdateDisplay (void)
{
  switch (state) {
    
  case CTR_STATE_ROOT:
    LCD_Display_Root ();
    break;

  case CTR_STATE_MENU:
    LCD_Display_Menu ();
    break;

  default:
    LCD_Display_Error (1);
  }

  return;
}

// ===================================================================
//   handleButtonRIGHT()
// ===================================================================
static void handleButtonRIGHT (void)
{
  PRINTF ("RIGHT \n");
}

// ===================================================================
//   handleButtonLEFT()
// ===================================================================
static void handleButtonLEFT (void)
{
  PRINTF ("LEFT\n");
}

// ===================================================================
//   handleButtonUP()
// ===================================================================
static void handleButtonUP (void)
{
  PRINTF ("UP\n");
}

// ===================================================================
//   handleButtonDOWN()
// ===================================================================
static void handleButtonDOWN (void)
{
  PRINTF ("DOWN\n");
}

// ===================================================================
//   handleButtonSELECT()
// ===================================================================
static void handleButtonSELECT (void)
{
  PRINTF ("SELECT\n");

  if (state == CTR_STATE_ROOT) {
    // Going to menu
    state = CTR_STATE_MENU;
  }
  else if (state == CTR_STATE_MENU) {
    // Back to root
    state = CTR_STATE_ROOT;
  }

}

// ===================================================================
//   CMD_handle
//
//   LCD & Keypad commands from menu:
//
//     TEMPERATURE     -> show EXT, IN, OUT (can change curve from OUT)
//                 
//     INFO
//
//   Internet commands:
//
//     ??
// ===================================================================
void CMD_handle (uint8_t command)
{
  //PRINTF (" *** Received command 0x%02x ... \n", command);

  switch (command) {

  case CMD_NONE:
    break;

  case CMD_BUTTON_RIGHT:
    handleButtonRIGHT();
    break;

  case CMD_BUTTON_UP:
    handleButtonUP();
    break;

  case CMD_BUTTON_DOWN:
    handleButtonDOWN();
    break;

  case CMD_BUTTON_LEFT:
    handleButtonLEFT();
    break;

  case CMD_BUTTON_SELECT:
    handleButtonSELECT();
    break;

  default:
    ASSERT(__FILE__,__LINE__);
  }

  return;
}

// ===================================================================
//   readButton
// ===================================================================
static uint8_t readButton (void)
{
  unsigned int buttonVoltage;

  uint8_t button = CMD_NONE; //BUTTON_NONE;
   
  //read the button ADC pin voltage
  buttonVoltage = analogRead (BUTTON_ADC_PIN);

  //PRINTF ("readButton: buttonVoltage=%u \n", buttonVoltage);

  //sense if the voltage falls within valid voltage windows
  if (buttonVoltage < ( RIGHT_10BIT_ADC + BUTTONHYSTERESIS )) {
    button = CMD_BUTTON_RIGHT;
  }
  else if (buttonVoltage >= ( UP_10BIT_ADC - BUTTONHYSTERESIS ) && buttonVoltage <= ( UP_10BIT_ADC + BUTTONHYSTERESIS )) {
    button = CMD_BUTTON_UP;
  }
  else if (buttonVoltage >= ( DOWN_10BIT_ADC - BUTTONHYSTERESIS ) && buttonVoltage <= ( DOWN_10BIT_ADC + BUTTONHYSTERESIS )) {
    button = CMD_BUTTON_DOWN;
  }
  else if (buttonVoltage >= ( LEFT_10BIT_ADC - BUTTONHYSTERESIS ) && buttonVoltage <= ( LEFT_10BIT_ADC + BUTTONHYSTERESIS )) {
    button = CMD_BUTTON_LEFT;
  }
  else if (buttonVoltage >= ( SELECT_10BIT_ADC - BUTTONHYSTERESIS ) && buttonVoltage <= ( SELECT_10BIT_ADC + BUTTONHYSTERESIS )) {
    button = CMD_BUTTON_SELECT;
  }
   
  return( button );
}

// ===================================================================
//   PollButtons
//
//   For now we cannot use interrupts, so just call this inside the
//   loop; includes debouncing
// ===================================================================
uint8_t PollButtons (void)
{
  //uint32_t now;  // = getTickCount();

  uint8_t button = CMD_NONE;
  uint8_t tmpButtonState = readButton();  //BUTTON_NONE;

  PRINTF ("PollButtons: tmpButtonState=%u \n", tmpButtonState);
  PRINTF ("PollButtons: lastButtonState=%u \n", lastButtonState);

  // check to see if you just pressed the button 
  // (i.e. the input went from LOW to a buttonState),  and you've waited 
  // long enough since the last press to ignore any noise:  

  // If the switch changed, due to noise or pressing:
  if (tmpButtonState != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = getTickCount();
  }

  PRINTF ("PollButtons: lastDebounceTime=%u \n", lastDebounceTime);

  uint32_t diff = getTickCount() - lastDebounceTime;

  //PRINTF ("PollButtons: time=%u \n", now);

  //uint32_t diff = now - lastTime;

  PRINTF ("PollButtons: diff=%u \n", diff);

  if (diff > DEBOUNCE_TICKS) {
    //
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:
    //
    //button = readButton();
    button = tmpButtonState;
  }

  //lastTime = now;
  //
  // save the reading.  Next time through the loop,
  // it'll be the lastButtonState:
  //
  lastButtonState = tmpButtonState;

  return button;
}

// ===================================================================
//   button_isr
// ===================================================================
void ISR_buttonHandle (void) __attribute__((naked));
void ISR_buttonHandle (void)
{
  //portBASE_TYPE xHigherPriorityTaskWoken;
  //BaseType_t xHigherPriorityTaskWoken;

  //uint32_t time;

  portSAVE_CONTEXT();

  /* We have not woken a task at the start of the ISR. */
  //xHigherPriorityTaskWoken = pdFALSE;
  
  uint8_t button = CMD_NONE;
  uint8_t tmpButtonState = readButton();

  // check to see if you just pressed the button 
  // (i.e. the input went from LOW to a buttonState),  and you've waited 
  // long enough since the last press to ignore any noise:  

  // If the switch changed, due to noise or pressing:
  if (tmpButtonState != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = getTickCount();
  }

  uint32_t diff = getTickCount() - lastDebounceTime;

  if (diff > DEBOUNCE_TICKS) {
    //
    // This variable is used for the call to xQueueSendToBackFromISR()
    // We do not use it to decide whether or not we do context switch,
    // we always do context switch => TRUE
    //
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    //uint8_t cmd = readButton();
    button = tmpButtonState;

    //
    // Push data to the queue
    //

    //while(uart0_hasdata()) {
    //  c = uart0_getchar();

    if (xQueueSendToBackFromISR(CmdQueue, &button, &xHigherPriorityTaskWoken) != pdPASS) {
      //dprint("uart0_irqhandler(): queue full\n\r");
      // Queue is full
      // TODO: perhaps do something ?
    }
    //}
  }

  //lastTime = time;
  //
  // save the reading.  Next time through the loop,
  // it'll be the lastButtonState:
  //
  lastButtonState = tmpButtonState;

  /* Now the buffer is empty we can switch context if necessary. */
  //portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
  portYIELD_FROM_ISR(pdTRUE);

  portRESTORE_CONTEXT();
}

// ===================================================================
//   CMD_setup
// ===================================================================
void CMD_setup (void)
{
  //button adc input
  pinMode( BUTTON_ADC_PIN, INPUT );         //ensure A0 is an input
  digitalWrite( BUTTON_ADC_PIN, LOW );      //ensure pullup is off on A0

  state = CTR_STATE_ROOT;

  //lastTime = 0;
  lastButtonState = CMD_NONE;
}

#if 0
//
// This is an example of using port-expander -- looks very interesting:
//   http://www.gammon.com.au/forum/?id=10945
//
//   The solution to most of these problems is to use an I/O port expander, like the MCP23017, and interrupts. The port expander gives you 16 inputs, but only uses 2 pins on the Arduino (SDA and SCL), plus ground and power. Using interrupts lets you react to button presses even if you are doing something else when it is pressed. To do this we make use of the MCP23017's ability to generate an interrupt signal, and use one more wire to connect to one of the pins of the Arduino which can generate processor interrupts.
//

/// Author: Nick Gammon
// Date: 19 February 2011

// Demonstration of an interrupt service routine connected to the MCP23017

#include <Wire.h>

// MCP23017 registers (everything except direction defaults to 0)

#define IODIRA   0x00   // IO direction  (0 = output, 1 = input (Default))
#define IODIRB   0x01
#define IOPOLA   0x02   // IO polarity   (0 = normal, 1 = inverse)
#define IOPOLB   0x03
#define GPINTENA 0x04   // Interrupt on change (0 = disable, 1 = enable)
#define GPINTENB 0x05
#define DEFVALA  0x06   // Default comparison for interrupt on change (interrupts on opposite)
#define DEFVALB  0x07
#define INTCONA  0x08   // Interrupt control (0 = interrupt on change from previous, 1 = interrupt on change from DEFVAL)
#define INTCONB  0x09
#define IOCON    0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
//#define IOCON 0x0B  // same as 0x0A
#define GPPUA    0x0C   // Pull-up resistor (0 = disabled, 1 = enabled)
#define GPPUB    0x0D
#define INFTFA   0x0E   // Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
#define INFTFB   0x0F
#define INTCAPA  0x10   // Interrupt capture (read only) : value of GPIO at time of last interrupt
#define INTCAPB  0x11
#define GPIOA    0x12   // Port value. Write to change, read to obtain value
#define GPIOB    0x13
#define OLLATA   0x14   // Output latch. Write to latch output.
#define OLLATB   0x15


#define port 0x20  // MCP23017 is on I2C port 0x20

#define ISR_INDICATOR 12  // pin 12
#define ONBOARD_LED 13    // pin 13

volatile boolean keyPressed;

// set register "reg" on expander to "data"
// for example, IO direction
void expanderWriteBoth (const byte reg, const byte data ) 
{
  Wire.beginTransmission (port);
  Wire.send (reg);
  Wire.send (data);  // port A
  Wire.send (data);  // port B
  Wire.endTransmission ();
} // end of expanderWrite

// read a byte from the expander
unsigned int expanderRead (const byte reg) 
{
  Wire.beginTransmission (port);
  Wire.send (reg);
  Wire.endTransmission ();
  Wire.requestFrom (port, 1);
  return Wire.receive();
} // end of expanderRead

// interrupt service routine, called when pin D2 goes from 1 to 0
void keypress ()
{
  digitalWrite (ISR_INDICATOR, HIGH);  // debugging
  keyPressed = true;   // set flag so main loop knows
}  // end of keypress

void setup ()
{
  pinMode (ISR_INDICATOR, OUTPUT);  // for testing (ISR indicator)
  pinMode (ONBOARD_LED, OUTPUT);  // for onboard LED

  Wire.begin ();  
  Serial.begin (9600);  

  // expander configuration register
  expanderWriteBoth (IOCON, 0b01100000); // mirror interrupts, disable sequential mode
 
  // enable pull-up on switches
  expanderWriteBoth (GPPUA, 0xFF);   // pull-up resistor for switch - both ports

  // invert polarity
  expanderWriteBoth (IOPOLA, 0xFF);  // invert polarity of signal - both ports
  
  // enable all interrupts
  expanderWriteBoth (GPINTENA, 0xFF); // enable interrupts - both ports
  
  // no interrupt yet
  keyPressed = false;

  // read from interrupt capture ports to clear them
  expanderRead (INTCAPA);
  expanderRead (INTCAPB);
  
  // pin 19 of MCP23017 is plugged into D2 of the Arduino which is interrupt 0
  attachInterrupt(0, keypress, FALLING);
  
}  // end of setup

// time we turned LED on
unsigned long time = 0;

// called from main loop when we know we had an interrupt
void handleKeypress ()
{
  unsigned int keyValue = 0;
  
  delay (100);  // de-bounce before we re-enable interrupts
  
  keyPressed = false;  // ready for next time through the interrupt service routine
  digitalWrite (ISR_INDICATOR, LOW);  // debugging
  
  // Read port values, as required. Note that this re-arms the interrupts.
  if (expanderRead (INFTFA))
    keyValue |= expanderRead (INTCAPA) << 8;    // read value at time of interrupt
  if (expanderRead (INFTFB))
    keyValue |= expanderRead (INTCAPB);        // port B is in low-order byte
  
  // display which buttons were down at the time of the interrupt
  for (byte button = 0; button < 16; button++)
    {
    // this key down?
    if (keyValue & (1 << button))
      {
      Serial.print ("Button ");
      Serial.print (button + 1, DEC);
      Serial.println (" now down");
      }  // end of if this bit changed
    
    } // end of for each button
  
  // if a switch is now pressed, turn LED on  (key down event)
  if (keyValue)
    {
    time = millis ();  // remember when
    digitalWrite (ONBOARD_LED, HIGH);  // on-board LED
    }  // end if
  
}  // end of handleKeypress

void loop ()
{
volatile unsigned long i;

  // some important calculations here ...
  for (i = 0; i < 0x7FFFF; i++)
    {}
  
  // was there an interrupt?
  if (keyPressed)
    handleKeypress ();

  // turn LED off after 500 ms 
 if (millis () > (time + 500) && time != 0)
   {
    digitalWrite (ONBOARD_LED, LOW);
    time = 0;
   }  // end if time up
 
}  // end of loop

#endif // 0

